#pragma once

#include "type_traits.hpp"

namespace zuck
{
	template<typename K, typename V>
	class pair {
		using _Self = pair<K, V>;
	public:
		using key_type = K;
		using val_type = V;
		using value_type = pair<K, V>;
		using pointer = pair<K, V>*;
		using const_pointer = const pair<K, V>*;
		using reference = _Self&;
		using const_reference = const _Self&;
	public:
		K first;
		V second;
	public:
		constexpr explicit pair
		(const K& _key = K{}, const V& _val = V{})
			: first(_key), second(_val)
		{}

		pair(const pair& other)
			: first(other.first)
			, second(other.second)
		{}

		inline pair(pair&& other)noexcept
			: first(other.first)
			, second(other.second)
		{
			other.first.~K();
			other.second.~V();
		}

		inline pair& operator=(const pair& other)
		{
			first = other.first;
			second = other.second;
			return *this;
		}

		inline pair& operator=(pair&& other)noexcept
		{
			first  = other.first;
			second = other.second;
			first.~K();
			second.~V();
			return *this;
		}

		[[nodiscard]] constexpr bool operator==(const pair& other)const noexcept
		{
			return static_cast<bool>(first == other.first && second == other.second);
		}

		[[nodiscard]] constexpr bool operator!=(const pair& other)const noexcept
		{
			return static_cast<bool>(!(*this == other));
		}

		[[nodiscard]] constexpr bool operator<(const pair& other)const noexcept
		{
			return static_cast<bool>(first < other.first || 
				                     (!(other.first < first) && second < other.second));
		}

		[[nodiscard]] constexpr bool operator>(const pair& other)const noexcept
		{
			return (other < *this);
		}

		[[nodiscard]] inline bool operator<=(const pair& other)const noexcept
		{
			return !(other < *this);
		}

		[[nodiscard]] inline bool operator>=(const pair& other)const noexcept
		{
			return !(*this < other);
		}

	};

	template<typename K, typename V>
	pair(const K&, const V&)->pair<K, V>;

	template<typename K, typename V>
	pair(K&&, V&&)->pair<K, V>;

	template<typename K, typename V>
	pair(const pair<K, V>&)->pair<K, V>;

	template<typename K, typename V>
	pair(pair<K, V>&&)->pair<K, V>;


	template<typename T1, typename T2, bool = is_empty_v<T1>>
	class compressed_pair final : private T1 {
	public:
		T2 _Myval2;

		using _Base = T1;

		constexpr compressed_pair() : _Myval2() {}

		template<typename ...Other2>
		constexpr compressed_pair(Other2&&... val2)
			: T1(), _Myval2(::std::forward<Other2>(val2)...) {}

		constexpr T1& get_first() { return *this; }

		constexpr const T1& get_first()const { return *this; }

	};

	template<typename T1, typename T2>
	class compressed_pair<T1, T2, false> final {
	public:
		using type1 = T1;
		using type2 = T2;
	public:
		T1 _M_val1;
		T2 _M_val2;
	public:
		constexpr compressed_pair() : _M_val1(), _M_val2() {}

		constexpr compressed_pair(const T1& val1, const T2& val2)
			: _M_val1(val1), _M_val2(val2) {}

		template<typename Other1, typename ...Other2>
		constexpr compressed_pair(Other1&& val1, Other2&&... val2)
			: _M_val1(::std::forward<Other1>(val1)),
			_M_val2(::std::forward<Other2>(val2)...)
		{}

		constexpr T1& get_first() { return _M_val1; }

		constexpr const T1& get_first()const { return _M_val1; }

	};
}
